// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <algorithm>
#include <functional>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "coin2/base/request/session_pool.h"
#include "coin2/base/conversion.h"
#include "coin2/base/string_util.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/symbol_updater.h"
#include "coin2/exchange/base/api_util/raw_subscriber_util.h"
#include "coin2/exchange/coingecko/symbology/product.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::coingecko::feed_v3 {
namespace impl {

class CoingeckoSymbolUpdater : public base::feed::SymbolUpdater {
 public:
  explicit CoingeckoSymbolUpdater(const MarketExchangeApi& mea)
      : SymbolUpdater(mea) {
    static std::string coins_markets_str[3]{};
    bool exceeded_rate_limit = false;
    query_ts_ = GetCurrentTimestamp();
    for (int i = 0; i < 3; ++i) {
      if (coins_markets_str[i].empty()) {
        coins_markets_str[i] = coin2::base::request::GetHttpsContent(
            "api.coingecko.com",
            fmt::format("/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&"
                        "per_page=250&page={}&sparkline=false", i + 1));
      }
      rapidjson::Document document;
      document.Parse(coins_markets_str[i].data());

      if(document.HasParseError() || (document.IsObject() && document.HasMember("status") && document["status"].HasMember("error_code"))){
        LOG(INFO) << "query failed, use local cache";
        exceeded_rate_limit = true;
        break;
      }

      for (const auto& coin_info_json : document.GetArray()) {
        // Use upper case for PI within the program
        symbols_market_cap_desc_.emplace_back(StringToUpperCopy(coin_info_json["symbol"].GetString()));
        native_symbol_to_id_.emplace(
            StringToUpperCopy(coin_info_json["symbol"].GetString()),
            coin_info_json["id"].GetString());
      }
    }
    
    if (exceeded_rate_limit){
      symbols_market_cap_desc_.clear();
      rapidjson::Document document;
      std::ifstream ifs("./data/coin2/feed/coingecko_top500_symbol.json");
      std::string coins_markets_cache((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
      document.Parse(coins_markets_cache.data());
      if (document.HasParseError()) {
        throw std::runtime_error(
            std::string("JSON parse error: ") + GetParseError_En(document.GetParseError()));
      }
      for (const auto& coin_info_json : document.GetArray()) {
        // Use upper case for PI within the program
        symbols_market_cap_desc_.emplace_back(StringToUpperCopy(coin_info_json["symbol"].GetString()));
        native_symbol_to_id_.emplace(
            StringToUpperCopy(coin_info_json["symbol"].GetString()),
            coin_info_json["id"].GetString());
      }
    }
  }
  std::vector<std::pair<std::string, std::string>> GetUpdatedNormSymbols(
      const std::string& norm_template) override {
    std::vector<std::pair<std::string, std::string>> to_update;
    if (norm_template.find("*") != std::string::npos) {
      for (const auto& _symbol : symbols_market_cap_desc_) {
        if (_symbol.empty()) continue;
        std::string native_symbol = GetIdForNativeSymbol(_symbol);
        auto p = symbology::CoingeckoProduct::FromNativeStrNoThrow(
            native_symbol, query_ts_);
        if (!p) continue;
        to_update.push_back({p->symbol(), native_symbol});
      }
    } else {
      std::string native = symbology::CoingeckoProduct::FromNormStr(
          norm_template, query_ts_).native_subscription_symbol();
      to_update.push_back({norm_template, native});
    }
    return to_update;
  }
  std::string GetIdForNativeSymbol(const std::string& native_symbol) {
    return native_symbol_to_id_[native_symbol];
  }

 private:
  std::vector<std::string> symbols_market_cap_desc_;
  std::unordered_map<std::string, std::string> native_symbol_to_id_;
  int64_t query_ts_;
};
}  // namespace impl
using impl::CoingeckoSymbolUpdater;
}  // namespace coin2::exchange::coingecko::feed_v3
